iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0
Python

在AWS上進行物聯網與人工智慧實作系列 第 6

D06-Python 函數與模組

  • 分享至 

  • xImage
  •  

Python 函數與模組

目錄

定義函數

新增一個加總的函數 sumab(),關鍵字 def 引入一個函數定義,它必須後跟函數名稱帶括號的形式參數列表。構成函數體的語句從下一行開始,並且必須縮進。

def sumab(a,b):
    return a+b
  
print(sumab(10,12))
print(sumab)
sumx = sumab
print(sumx(20,22))
print(sumx)

---------------------------------------
# 輸出結果如下:
22
<function sumab at 0x000001F5AC16F040>
42
<function sumab at 0x000001F5AC16F040>

函數的執行會引入一個用於函數局部變數的新符號表。 更確切地說,函數中所有的變數賦值都將儲存在局部符號表中;而變數引用會首先在局部符號表中查找,然後是外層函數的局部符號表,再然後是全局符號表,最後是內建名稱的符號表。 因此,全局變數和外層函數的變數不能在函數內部直接賦值(除非是在 global 語句中定義的全局變數,或者是在 nonlocal 語句中定義的外層函數的變數)。在函數被呼叫時,實際參數(實參)會被引入被呼叫函數的本地符號表中;因此,實參是通過按值呼叫 (call by reference) 傳遞的(其中值始終是物件引用而不是物件的值)。當一個函數呼叫另外一個函數時,將會為該呼叫新建一個新的本地符號表。

函數定義會把函數名引入當前的符號表中。函數名稱的值具有直譯器將其識別為用戶定義函數的類型。這個值可以分配給另一個名稱,該名稱也可以作為一個函數使用,這用作一般的重命名機制。

return 語句會從函數內部回傳一個值。 不帶表達式參數的 return 會回傳 None。 函數執行完畢退出也會回傳 None。

參數預設值(Default Parameter Values)

給函數定義有可變數量的參數也是可行的,最有用的形式是對一個或多個參數指定一個預設值。這樣新建的函數,可以用比定義時允許的更少的參數呼叫,比如

def ask_ok(prompt, retries=4, reminder='Please try again!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)
  
# 只給出必需的參數:
ask_ok('Do you really want to quit?')
  
# 給出一個可選的參數:
ask_ok('OK to overwrite the file?', 2)
  
# 給出所有的參數:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
  

這個範例還介紹了 in 關鍵字。它可以測試一個列表是否包含某個值。

執行函數定義時,預設參數值從左到右計算,這意味著在定義函數時,表達式會被計算一次,並且每次呼叫都會使用相同的「預先計算」值。所以下面的範例為 5 。

i = 5
def f(arg=i):
    print(arg)
  
i = 6
f()
--------------------------
# 輸出結果如下:
5

當預設參數值是可變物件(例如列表或字典)時,函數修改物件(例如,透過將項目附加到列表),則預設參數值實際上會被修改。比如,下面的函數會儲存在後續呼叫中傳遞給它的參數:

def f1(a, L=[]):
    L.append(a)
    print('L address is ',id(L))  
    return L
  
print(f1(1))
print(f1(2))
print(f1(3))
  
--------------------------
# 輸出結果如下:
[1]
[1, 2]
[1, 2, 3]

但當預設參數值是不可變物件(例如字串或None)時,在函數中修改物件並不會在後續呼叫共享這個預設值,可以這樣寫這個函數:

def f2(a, L=None):
    print('L type is ',type(L))  
    if L is None:
        print('L is None')
        L = []
    print('L type is ',type(L))  
    L.append(a)
    return L

print(f2(1))
print(f2(2))

--------------------------
# 輸出結果如下:
L type is  <class 'NoneType'>
L is None
L type is  <class 'list'>
[1]

L type is  <class 'NoneType'>
L is None
L type is  <class 'list'>
[2]

而詳細原因建議參考 Fredrik Lundh 這篇文章 Default Parameter Values in Python,基本的概念是函數也是物件,而參數是物件內的屬性,而當這個屬性是可修改的時,資料就會被保留下來;但當屬性是不可修改的時候,對這個屬性的修改會生成一個新的記憶體參照,而不是這個屬性本身,所以下一次再呼叫這個函數時,就不會被記錄下來。

關鍵字參數(keyword arguments)

可以使用 kwarg=value 的關鍵字參數來呼叫函數。例如下面的函數:

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")

接受一個必需的參數(required arguments) - voltage 和三個可選的參數(state, action,和 type)。這個函數可以通過下面的任何一種方式呼叫:

parrot(1000)                                          # 1 位置參數 (positional argument)
parrot(voltage=1000)                                  # 1 關鍵字參數 (keyword argument)
parrot(voltage=1000000, action='VOOOOOM')             # 2 關鍵字參數
parrot(action='VOOOOOM', voltage=1000000)             # 2 關鍵字參數
parrot('a million', 'bereft of life', 'jump')         # 3 位置參數
parrot('a thousand', state='pushing up the daisies')  # 1 位置參數, 1 關鍵字參數

但下面的函數呼叫都是無效的:

parrot()                     # 缺乏必需參數
parrot(voltage=5.0, 'dead')  # non-keyword argument after a keyword argument
parrot(110, voltage=220)     # 相同參數重復給值
parrot(actor='John Cleese')  # 不認識的關鍵字參數

在函數呼叫中,關鍵字參數必須跟隨在位置參數的後面。傳遞的所有關鍵字參數必須與函數接受的其中一個參數匹配,它們的順序並不重要。這也包括非可選參數,(比如 parrot(voltage=1000) 也是有效的)。不能對同一個參數多次賦值。

*args 任意參數(arbitrary arguments):程式設計師不知道要傳遞給函數的參數數量,可以接收任意數量的位置參數,即非關鍵字參數、可變長度參數清單的參數。出現在 *args 參數之後的任何形式參數都是 ‘關鍵字參數’,也就是說它們只能作為關鍵字參數而不能是位置參數。

def concat(*args, sep='/'):
    if (sep == ''):
        return list(args)
    else:
        sep.join(args)
  
print(concat("earth", "mars", "venus"))
print(concat("earth", "mars", "venus", sep="|"))
  
------------------------------------------
['earth', 'mars', 'venus']
earth|mars|venus

lambda 表達式

可以用 lambda 關鍵字來新建一個小的匿名函數。這個函數回傳兩個參數的和: lambda a, b: a+b 。lambda 函數可以在需要函數物件的任何地方使用,它在語法上限於單個表達式。從語義上來說,只是正常函數定義的語法,可以引用所包含域的變數:

def make_incrementor(n):
    return lambda x: x + n
  
f = make_incrementor(42)
print(f(0)) # x=0, n=42
print(f(1)) # x=1, n=42
  
------------------------------------------
# 輸出結果如下:
42
43

模組

從 Python 直譯器退出並再次進入,之前的定義(函數和變數)都會丟失。因此,如果想編寫一個稍長些的程序,最好使用一般編輯器為直譯器準備輸入並將該文件作為輸入執行。這被稱作編寫腳本 (script) 。隨著程序變得越來越長,你或許會想把它拆分成幾個文件,以方便維護。你亦或想在不同的程序中使用一個便捷的函數,而不必把這個函數複製到每一個程序中去。為支持這些需求,Python 有一種方法可以把定義放在一個文件裡,並在腳本或直譯器的互動式實例中使用它們。這樣的文件被稱作模組 (module),模組中的定義可以匯入 (import) 到其它模組或者主模組。

模組是一個包含 Python 定義和語句的文件。文件名就是模組名後跟附檔名 .py 。在一個模組內部,模組名可以通過全局變數 __name__ 的值獲得。例如,使用你最喜愛的文本編輯器在當前目錄下新建一個名為 calc.py 的文件, 文件中含有以下內容:

calc.py

def add(a,b):
    return a + b
def sub(a,b):
    return a - b

現在進入 Python 直譯器,並用以下命令匯入該模組,在當前的符號表中,這並不會直接進入到定義在 add/sub 函數內的名稱,它只是進入到模組名 calc 中。你可以用模組名訪問這些函數

>>>
>>> import calc
>>> calc.add(10,20)
30
>>> calc.sub(30,20)
10
>>> calc.__name__
'calc'

import 語句有一個變體,它可以把名字從一個被呼叫模組內直接匯入到現模組的符號表裡;可以匯入模組內定義的所有名稱,這會調入所有非以下划線(_)開頭的函數名稱,但在多數情況下,Python 程序員都不會使用這個功能,因為它在直譯器中引入了一組未知的名稱,而它們很可能會覆蓋一些你已經定義過的東西;模組名稱之後帶有 as,則跟在 as 之後的名稱將直接綁定到所匯入的模組,這種方式也可以在用到 from 的時候使用,並會有類似的效果;內建函數 dir() 用於查找模組定義的名稱。 它回傳一個排序過的字符串列表,列出所有類型的名稱:變數,模組,函數,等等。

>>> from calc import add, sub
>>> add(50,20)
70
>>> from calc import *
>>> add(50,20)
70
>>> import calc as c
>>> c.add(50,20)
70
>>> from calc import add as addOperation
>>> addOperation(50,20)
70
>>> dir(calc)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add', 'sub']

當要匯入 calc 模組的時候,直譯器首先尋找具有該名稱的內建模組。如果沒有找到,然後直譯器從 sys.path 變數給出的目錄列表裡尋找名為 calc.py 的文件。sys.path 初始有這些目錄地址:

  • 包含輸入腳本的目錄(或者未指定文件時的當前目錄)。
  • PYTHONPATH (一個包含目錄名稱的列表,它和 shell 變數 PATH 有一樣的語法)。
  • 取決於安裝的預設設置

參考資料


上一篇
D05 - Python 分支控制
下一篇
D07-ESP32-CAM 簡介
系列文
在AWS上進行物聯網與人工智慧實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言